/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.idea.utils.sfo.designer;


import designersfo.Cons.CellInfo.CellFormat;
import designersfo.Cons.CellInfo.CustomContentCellData;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.InvocationTargetException;
import javax.swing.undo.AbstractUndoableEdit;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import org.openide.awt.UndoRedo;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.Exceptions;
import org.openide.util.lookup.Lookups;


/**
 *
 * @author renke002
 */
public class DesignerColNode extends AbstractNode implements UndoRedo.Provider , PropertyChangeListener
{

    /**
     * @XmlAttribute(name = ) protected String ;
     * @XmlAttribute(name = "tab-order") protected String tabOrder;
     * @XmlAttribute(name = "background-color") protected String
     * backgroundColor;
     * @XmlAttribute(name = ) protected String response;
     *
     */
    public final static String[][] CELL_DATA_PROPS =
    {
        {
            "content-id" , "getContentId" , "setContentId"
        } ,
        {
            "relative-index" , "getRelativeIndex" , "setRelativeIndex"
        } ,
        {
            "response" , "getResponse" , "setResponse"
        }
    };
    /**
     *
     * @( name = "border-rb-corner-radius" ) protected String
     * getBorderRbCornerRadius;
     * @XmlAttribute( name = "border-right-style" ) protected String
     * getBorderRightStyle;
     * @XmlAttribute( name = "border-right-color" ) protected String
     * getBorderRightColor;
     * @XmlAttribute( name = "border-right-width" ) protected String
     * getBorderRightWidth;
     * @XmlAttribute( name = "border-rt-corner-radius" ) protected String
     * getBorderRtCornerRadius;
     * @XmlAttribute( name = "border-top-style" ) protected String
     * getBorderTopStyle;
     * @XmlAttribute( name = "border-top-color" ) protected String
     * getBorderTopColor;
     * @XmlAttribute( name = "border-top-width" ) protected String
     * getBorderTopWidth;
     * @XmlAttribute( name = "border-lt-corner-radius" ) protected String
     * getBorderLtCornerRadius;
     * @XmlAttribute( name = "border-left-style" ) protected String
     * getBorderLeftStyle;
     * @XmlAttribute( name = "border-left-color" ) protected String
     * getBorderLeftColor;
     * @XmlAttribute( name = "border-left-width" ) protected String
     * getBorderLeftWidth;
     * @XmlAttribute( name = "border-lb-corner-radius" ) protected String
     * getBorderLbCornerRadius;
     * @XmlAttribute( name = "border-bottom-style" ) protected String
     * getBorderBottomStyle;
     * @XmlAttribute( name = "border-bottom-color" ) protected String
     * getBorderBottomColor;
     * @XmlAttribute( name = "border-bottom-width" ) protected String
     * getBorderBottomWidth;
     *
     */
    public final static String[][] CELL_FORMAT_PROPS =
    {
        /*
         * 
         */
        {
            "background-color" , "getBackgroundColor" , "setBackgroundColor"
        } ,
        {
            "font-family" , "getFontFamily" , null
        } ,
        {
            "font-size" , "getFontSize" , null
        } ,
        {
            "font-color" , "getFontColor" , null
        } ,
        {
            "font-style" , "getFontStyle" , null
        } ,
        {
            "font-weight" , "getFontWeight" , null
        } ,
        {
            "font-decoration" , "getFontDecoration" , null
        } ,
        {
            "font-rotation-angle" , "getFontRotationAngle" , null
        } ,
        {
            "margin-color" , "getMarginColor" , null
        } ,
        {
            "padding-bottom" , "getPaddingBottom" , null
        } ,
        {
            "padding-left" , "getPaddingLeft" , null
        } ,
        {
            "padding-top" , "getPaddingTop" , null
        } ,
        {
            "padding-right" , "getPaddingRight" , null
        } ,
        {
            "has-leader" , "getHasLeader" , null
        } ,
        {
            "has-goomer" , "getHasGoomer" , null
        } ,
        {
            "horz-alignment" , "getHorzAlignment" , null
        } ,
        {
            "vert-alignment" , "getVertAlignment" , null
        }
    };
    /**
     * Position of the attribute name in the Cell Format Properties array.
     */
    public final static int CELL_PROPS_NAME = 0;
    /**
     * Position of the getter function name in the Cell Format Properties array.
     */
    public final static int CELL_PROPS_GETTER = 1;
    /**
     * Position of the setter function name in the Cell Format Properties array.
     */
    public final static int CELL_PROPS_SETTER = 2;
    
    
    private UndoRedo.Manager manager = new UndoRedo.Manager();
    private boolean undoRedoEvent;


    /**
     *
     * @param dCol
     * @throws IntrospectionException
     */
    public DesignerColNode( DesignerCol dCol ) throws IntrospectionException
    {
        super( Children.LEAF , Lookups.fixed( new Object[]
        {
            dCol
        } ) );

        dCol.addPropertyChangeListener( this );

        this.setDisplayName( dCol.getName() );
    }


    /**
     * Since this class is extending an org.openide.nodes.AbstractNode, we need
     * to implement our own version of createSheet() to display the properties
     * we want the user to have access to.
     *
     * @return
     */
    @Override
    protected Sheet createSheet()
    {
        Sheet sheet = Sheet.createDefault();

        DesignerCol dCol = getLookup().lookup( DesignerCol.class );

        //
        sheet.put( this.createMainPropSheetSet( dCol ) );

        if ( dCol.containsCellInfo() )
        {
            if ( dCol.containsCellData() )
            {
                sheet.put( this.createCellDataPropSheetSet( dCol ) );
            }


            if ( dCol.containsCellFormat() )
            {
                // Cell-Format contains many fields, so it's best to display it on a
                // separate tab from Cell-Info & Cell-Data
                sheet.put( createCellFormatPropSheetSet( dCol ) );
            }
        }


        //
        return sheet;
    }


    /**
     *
     * @param dCol
     * @return
     */
    private Sheet.Set createCellFormatPropSheetSet( DesignerCol dCol )
    {
        Sheet.Set cellFormatSet = Sheet.createPropertiesSet();
        cellFormatSet.setDisplayName( "Cell Format" );
        cellFormatSet.setName( "Cell Format" );

        // this code will create a new tab under the properties window.
        // Cell-Format contains many fields, so it's best to display it on a
        // separate tab from Cell-Info & Cell-Data
        cellFormatSet.setValue( "tabName" , "Cell Format" );

        CellFormat cellFormat = dCol.getCellFormat();

        for ( int i = 0; i < DesignerColNode.CELL_FORMAT_PROPS.length; i++ )
        {
            try
            {
                /*
                 * DesignerColNode.CELL_FORMAT_PROPS[i] -> {
                 *      ATTRIBUTE_NAME ,
                 *      GETTER_FUNCTION_NAME ,
                 *      SETTER_FUNCTION_NAME ,
                 * }
                 */
                Property prop = new PropertySupport.Reflection(
                        cellFormat ,
                        String.class ,
                        DesignerColNode.CELL_FORMAT_PROPS[i][DesignerColNode.CELL_PROPS_GETTER] ,
                        DesignerColNode.CELL_FORMAT_PROPS[i][DesignerColNode.CELL_PROPS_SETTER] );
                prop.setName( DesignerColNode.CELL_FORMAT_PROPS[i][DesignerColNode.CELL_PROPS_NAME] );
                cellFormatSet.put( prop );
            } catch ( NoSuchMethodException ex )
            {
                Exceptions.printStackTrace( ex );
            }
        }

        return cellFormatSet;
    }


    /**
     *
     * @param dCol
     * @return
     */
    private Sheet.Set createCellDataPropSheetSet( DesignerCol dCol )
    {
        Sheet.Set cellDataSet = Sheet.createPropertiesSet();

        cellDataSet.setDisplayName( "Cell Data" );
        cellDataSet.setName( "CellData" );

        CustomContentCellData cd = dCol.getCellData();

        for ( int i = 0; i < DesignerColNode.CELL_DATA_PROPS.length; i++ )
        {
            try
            {
                /*
                 * DesignerColNode.CELL_FORMAT_PROPS[i] -> {
                 *      ATTRIBUTE_NAME ,
                 *      GETTER_FUNCTION_NAME ,
                 *      SETTER_FUNCTION_NAME ,
                 * }
                 */
                Property prop = new PropertySupport.Reflection(
                        cd ,
                        String.class ,
                        DesignerColNode.CELL_DATA_PROPS[i][DesignerColNode.CELL_PROPS_GETTER] ,
                        DesignerColNode.CELL_DATA_PROPS[i][DesignerColNode.CELL_PROPS_SETTER] );
                prop.setName( DesignerColNode.CELL_DATA_PROPS[i][DesignerColNode.CELL_PROPS_NAME] );
                cellDataSet.put( prop );
            } catch ( NoSuchMethodException ex )
            {
                Exceptions.printStackTrace( ex );
            }
        }
        return cellDataSet;
    }


    /**
     *
     * @param dCol
     * @return
     */
    private Sheet.Set createMainPropSheetSet( DesignerCol dCol )
    {
        Sheet.Set propSet = Sheet.createPropertiesSet();

        try
        {
            Property nameProp = new PropertySupport.Reflection( dCol , String.class , "getName" , "setName" );
            Property rowNumProp = new PropertySupport.Reflection( dCol , Integer.class , "getRowNumber" , null );
            Property rowHeightProp = new PropertySupport.Reflection( dCol , Long.class , "getHeight" , null );
            Property rowWidthProp = new PropertySupport.Reflection( dCol , Long.class , "getWidth" , null );

            nameProp.setName( "ID" );
            rowNumProp.setName( "Row #" );
            rowHeightProp.setName( "Height" );
            rowWidthProp.setName( "Width" );

            propSet.put( nameProp );
            propSet.put( rowNumProp );
            propSet.put( rowHeightProp );
            propSet.put( rowWidthProp );

        } catch ( NoSuchMethodException ex )
        {
            Exceptions.printStackTrace( ex );
        }

        return propSet;
    }


    /**
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals( Object obj )
    {
        if ( obj == null )
        {
            return false;
        }
        if ( getClass() != obj.getClass() )
        {
            return false;
        }
        final DesignerColNode other = (DesignerColNode) obj;
        final DesignerCol dCol = this.getDesignerCol();

        if ( dCol.getRowNumber().equals( other.getRowNumber() )
                && dCol.getColNumber().equals( other.getColNumber() ) )
        {
            // both object have the same row and column numbers, IE. they
            // share the same location.
            return true;
        } else
        {
            return false;
        }
    }


    /**
     *
     * @return
     */
    public Integer getColNumber()
    {
        return this.getDesignerCol().getColNumber();
    }


    /**
     *
     * @return
     */
    public DesignerCol getDesignerCol()
    {
        return getLookup().lookup( DesignerCol.class );
    }


    /**
     *
     * @return
     */
    public Integer getRowNumber()
    {
        return this.getDesignerCol().getRowNumber();
    }


    /**
     *
     * @return
     */
    @Override
    public int hashCode()
    {
        DesignerCol dc = this.getDesignerCol();
        int col = dc.getColNumber();
        int row = dc.getRowNumber();
        return Integer.parseInt( col + "" + row );
    }


    /**
     * Makes the border of this element visible or invisible.
     *
     * @param visible
     */
    public void setBorderVisible( boolean visible )
    {
        getLookup().lookup( DesignerCol.class ).setBorderVisible( visible );
    }


    /**
     * Tells this element to set its 'selected' behavior.
     *
     * @param selected
     */
    public void setSelected( boolean selected )
    {
        getLookup().lookup( DesignerCol.class ).setSelected( selected );
    }
// from here below is Ed's attempt to get Undo Redo working...

    @Override
    public UndoRedo getUndoRedo()
    {
        return manager;
    }


    @Override
    public void propertyChange( PropertyChangeEvent evt )
    {
//        DesignerCol dCol = this.getDesignerCol();
//        if ( evt.getPropertyName().equals( dCol.getName() ) )
//        {
            firePropertyChange( evt.getPropertyName() , evt.getOldValue() , evt.getNewValue() );
            fireUndoableEvent(evt.getPropertyName(), this.getDesignerCol(), evt.getOldValue(), evt.getNewValue());
//        }
    }


    public void fireUndoableEvent( String property , DesignerCol source , Object oldValue , Object newValue )
    {
        manager.addEdit( new MyAbstractUndoableEdit( source , oldValue , newValue ) );
    }


    @Override
    public String getDisplayName()
    {
        DesignerCol dCol = getLookup().lookup( DesignerCol.class );
        if ( dCol != null )
        {
            return dCol.getName();
        }
        return super.getDisplayName();
    }
    
    private class NameProperty extends PropertySupport.ReadWrite<String> {

        private DesignerCol dCol;
        
        public NameProperty(DesignerCol dCol) {
                super("name", String.class, "Name", "Name of DesignerCol");
        }
        
        @Override
        public String getValue() throws IllegalAccessException , InvocationTargetException
        {
            return dCol.getName();
        }


        @Override
        public void setValue( String newValue ) throws IllegalAccessException , IllegalArgumentException , InvocationTargetException
        {
            String oldValue = dCol.getName();
            dCol.setName( newValue );
            if (!undoRedoEvent) {
                fireUndoableEvent("name", dCol, oldValue, newValue);
                fireDisplayNameChange(oldValue, newValue);
            }
            
        }
        
    }
    
    class MyAbstractUndoableEdit extends AbstractUndoableEdit {
        private final String oldValue;
        private final String newValue;
        private final DesignerCol source;
        
        private MyAbstractUndoableEdit(DesignerCol source, Object oldValue, Object newValue) {
            this.oldValue = oldValue.toString();
            this.newValue = newValue.toString();
            this.source = source;
        }


        @Override
        public void undo() throws CannotUndoException
        {
            undoRedoEvent = true;
            source.setName( oldValue.toString());
            fireDisplayNameChange(oldValue, newValue);
            undoRedoEvent = false;
        }


        @Override
        public boolean canUndo()
        {
            return true;
        }


        @Override
        public void redo() throws CannotRedoException
        {
            undoRedoEvent = true;
            source.setName( newValue.toString());
            fireDisplayNameChange(oldValue, newValue);
            undoRedoEvent = false;
        }


        @Override
        public boolean canRedo()
        {
            return true;
        }
        
        
    }

}
